import { PropType, computed, defineComponent, reactive, ref, watchEffect } from "vue";
import { createUniqueId } from "../use/createUniqueId";

export interface IndexListProps {
    data: any[];
    selectable?: boolean;
    promote?: boolean;
    border?: boolean;
    modelValue?: any[];
    renderItem?(item: any, active: boolean): any;
}

export default defineComponent({
    name: "IndexList",
    props: {
        data: {type: Array as PropType<any[]>},
        selectable: {type: Boolean, default: false},
        promote: {type: Boolean, default: true},
        border: {type: Boolean, default: false},
        modelValue: {type: Array as PropType<IndexListProps['modelValue']>, default: () => []},
        renderItem: {type: Function as PropType<(item: any, active: boolean) => any>},
    },
    emits: ['change'],
    setup (props, { emit }) {
        const promote = computed(() => props.promote ?? true);
        const value = ref<any[]>(props.modelValue ?? []);
        const activeAnchor = ref('');
        const showPromote = ref(false);
        const promoteText = ref('');
        const store = reactive({
            list: [],
            listMap: {}
        } as {
            list: any[],
            listMap: {[key: string|number]: any}
        });

        // 构建数据结构
        let map: {[key: string|number]: any} = {};
        const wrap = ref<HTMLDivElement | null>(null);
        const topMap: {[key: string|number]: number} = {};

        watchEffect(() => {
            const list: any[] = [];
            map = {};
            const listMap: {[key: string|number]: any} = {};
            props.data.forEach((item: any) => {
                if (item.id === undefined || item.id === null) {
                    item.id = createUniqueId();
                }
                const newItem: any = { id: item.id };
                map[item.id] = item;
                listMap[item.id] = newItem;
                list.push(newItem);
                if (item.children) {
                    newItem.children = [];
                    item.children.forEach((subItem: any) => {
                        if (subItem.id === undefined || subItem.id === null) {
                            subItem.id = createUniqueId();
                        }
                        map[subItem.id] = subItem;
                        const newSubItem = { id: subItem.id };
                        listMap[subItem.id] = newSubItem;
                        newItem.children.push(newSubItem);
                    });
                }
            });
            store.list = list;
            store.listMap = listMap;
        });
        const classList = computed(() => ({
            'cm-index-list': true,
            'cm-index-list-border': props.border
        }));

        const onItemClick = (subItem: any) => {
            if (!props.selectable) {
                return;
            }
            const val = value.value;
            const id = subItem.id;

            if (subItem.active) {
                const index = val.indexOf(id);
                val.splice(index, 1);
                value.value = val;
            } else {
                val.push(id);
                value.value = [...val];
            }
            emit('change', value.value);
            store.listMap[subItem.id].active = !subItem.active;
        };

        let promoteTimer: any = null;
        const gotoAnchor = (id: string, name: string, e: Event) => {
            if (e.preventDefault) {
                e.preventDefault();
            }
            if (e.stopPropagation) {
                e.stopPropagation();
            }
            const ele = document.querySelector(id);

            if (ele) {
                if (promote.value) {
                    promoteText.value = name;
                    showPromote.value = true;
                    if (promoteTimer) {
                        clearTimeout(promoteTimer);
                    }
                    promoteTimer = setTimeout(() => {
                        scrollEnd();
                    }, 1000);
                }
                const eleOff = ele.getBoundingClientRect().top;
                const parentOff = wrap.value.getBoundingClientRect().top;
                const scrollTop = eleOff - parentOff;
                wrap.value.scrollTo({
                    top: wrap.value.scrollTop + scrollTop,
                    behavior: "smooth",
                });
            }
        };

        const scrollEnd = () => {
            showPromote.value = false;
        };

        const handleScroll = () => {
            const scrollTop = wrap.value.scrollTop;
            const id = getAnchorByScrollTop(scrollTop);
            activeAnchor.value = id;
        };

        const getAnchorByScrollTop = (scrollTop: number) => {
            let minId = ''; let min = Number.MAX_VALUE;
            for (const id in topMap){
                const t = Math.abs(topMap[id] - scrollTop);
                if (min > t) {
                    min = t;
                    minId = id;
                }
            }
            return minId;
        };

        const initTop = (e: HTMLElement, id: string) => {
            queueMicrotask(() => {
                topMap[id] = e && e.offsetTop;
            });
        };

        const promoteClass = () => ({
            'cm-index-list-promote': true,
            'cm-index-list-promote-show': showPromote.value
        });

        return () => <div class={classList.value}>
            <div class="cm-index-list-list" ref={wrap} onScroll={handleScroll}>
                {
                    store.list.map(item => {
                        const dataItem = map[item.id];
                        return <dl id={`cm_index_list_${item.id}`} ref={(e: HTMLElement) => {
                            initTop(e, item.id);
                        }}>
                            <dt>{dataItem.name}</dt>
                            {
                                item.children && item.children.map(subItem => {
                                    const subDataItem = map[subItem.id];
                                    return <dd class={subItem.active ? 'active' : ''} onClick={onItemClick.bind(null, subItem)}>
                                        {props.renderItem ? props.renderItem(subDataItem, subItem.active) : subDataItem.name}
                                    </dd>;
                                })
                            }
                        </dl>;
                    })
                }
            </div>
            <div class="cm-index-list-nav">
                {
                    store.list.map(item => {
                        const dataItem = map[item.id];
                        const active = () => activeAnchor.value === item.id;
                        const classList = () => ({
                            'cm-index-list-nav-item': true,
                            'active': active()
                        });
                        return <div class={classList()} onClick={gotoAnchor.bind(null, `#cm_index_list_${item.id}`, dataItem.id)}>{dataItem.id}</div>;
                    })
                }
            </div>
            {
                promote.value
                    ? <div class={promoteClass()}>
                        {promoteText.value}
                    </div>
                    : null
            }
        </div>;
    }
});
